/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
import JaCoP.constraints.Alldistinct;
import JaCoP.constraints.Sum;
import JaCoP.constraints.SumWeight;
import JaCoP.constraints.XneqC;
import JaCoP.core.Constants;
import JaCoP.core.Store;
import JaCoP.core.Variable;
import JaCoP.search.DepthFirstSearch;
import JaCoP.search.IndomainMiddle;
import JaCoP.search.IndomainMin;
import JaCoP.search.MostConstrainedStatic;
import JaCoP.search.Search;
import JaCoP.search.SelectChoicePoint;
import JaCoP.search.SimpleSelect;
import java.util.ArrayList;
import java.util.HashMap;
/**
 * It class represends core algorithm to  solve any cryptographic puzzle. This creates a standard model using simple basic constraints.
 * @author Marek Synoradzki
 */
public class Cryptarithmer {
 private String firstWord;
    private String secondWord;
    private String thirdWord;
    private int base;
    private Store store;
    private ArrayList<String> word2s = new ArrayList<String>();;
    private HashMap<String, Variable> letters;
    private ArrayList<String> lista = new ArrayList<String>();
    private ArrayList<Variable> vars = new ArrayList<Variable>();
    public Search search;

    /**
     * Append all three words to special ArrayList to easly operates on given words
     * @param firstWord First word in cryptarithm
     * @param secondWord Second word in cryptarithm
     * @param thirdWord Third word in cryptarithm
     */
    public void wordsToArray(String firstWord, String secondWord, String thirdWord) {

        word2s.add(firstWord);
        word2s.add(secondWord);
        word2s.add(thirdWord);
    }

    /**
     * A constraint store can be considered as a manager of constraints and variables
     * It is always required to have a constraint store in order to create a CP model of the problem. 
     * @return Store store
     */
    public Store getStore()
    {
        return store;
    }

    /**
     * Return arrayList of Variable which represends nodes and constraints.
     * @return vars - List of Variable
     */
    public ArrayList<Variable> getVariablesArray()
    {
        return vars;
    }

    /**
     * Create Vaiables for letters. In this problem we will for certain have variables representing the value of different
     * letters. All new created variables are added to vars ArrayList
     */
    public void createVariableForLitters() {
        letters = new HashMap<String, Variable>();
        for (int j = word2s.size() - 1; j >= 0; j--)
        {
            for (int z = word2s.get(j).length() - 1; z >= 0; z--) {
                char[] currentChar = {word2s.get(j).charAt(z)};
                if (letters.get(new String(currentChar)) == null) {
                    Variable currentLetter = new Variable(store, new String(currentChar), 0, base-1);
                    vars.add(currentLetter);
                    letters.put(new String(currentChar), currentLetter);
                }
            }
        }

    }

/**
 * This method checks that amount of distnict letters in criptarithm is less than given base
 * @return true if given criptarithm could be solved, otherwise return false
 */
    public boolean isToSolve()
    {
       if (letters.size() > base)
           return false;
       else
           return true;
    }

    /**
     * Impose constrains that all letters in criptharithm have to have different value.
     */
    public void imposeAllDistinct()
    {
       store.impose(new Alldistinct(vars));
    }


    /**
     * Creates and imposes constraints which enforce relationship between letter and value of its position
     */
    public void createImpose()
    {
        int noWords = word2s.size();
        Variable[] fdv4words = new Variable[noWords];
        Variable[] terms = new Variable[noWords - 1];
        for (int j = 0; j < noWords; j++) {
            String currentWord = word2s.get(j);
            fdv4words[j] = new Variable(store, currentWord, 0, Constants.MaxInt);
            if (j < noWords - 1) {
                terms[j] = fdv4words[j];
            }
            Variable[] lettersWithinCurrentWord = new Variable[currentWord.length()];
            for (int i = 0; i < currentWord.length(); i++) {
                char[] currentChar = {currentWord.charAt(i)};
                lettersWithinCurrentWord[i] = letters.get(new String(
                        currentChar));
            }
            store.impose(new SumWeight(lettersWithinCurrentWord,
                    createWeights(currentWord.length(), base),
                    fdv4words[j]));
            store.impose(new XneqC(lettersWithinCurrentWord[0], 0));
        }
        store.impose(new Sum(terms, fdv4words[noWords - 1]));

    }



/**
 * Metod represends facade.
 * @param distinct true if all letters have to have different volue
 */
    public void run(boolean distinct)
    {
        wordsToArray(firstWord, secondWord, thirdWord);
        createVariableForLitters();
        if(isToSolve())
        {
            if(distinct) imposeAllDistinct();
        createImpose();
        }
        else
        {
            System.out.println("Liczba użytych liter w wyrazach przekracza max");
        }

    }

    /**
     * Constructor creates new Store object
     */
    public Cryptarithmer() {
        store = new Store();
    }


    /**
     *
     * @param firstWord first word in criptarithm
     * @param secondWord second word in criptarithm
     * @param thirdWord third word in criptarithm
     * @param base
     */
    public Cryptarithmer(String firstWord, String secondWord, String thirdWord, int base) {
        this.firstWord = firstWord;
        this.secondWord = secondWord;
        this.thirdWord = thirdWord;
        this.base = base;
        store = new Store();
    }

    /**
     * Set base
     * @param base base in criptharithm
     */
    public void setBase(int base) {
        this.base = base;
    }

    /**
     *
     * @return base in criptharithm
     */
    public int getBase() {
        return this.base;
    }

    /**
     *
     * @param firstWord first word in criptharithm
     */
    public void setFirstWord(String firstWord) {
        this.firstWord = firstWord;
    }

    /**
     *
     * @param secondWord second word in criptarithm
     */
    public void setSecondWord(String secondWord) {
        this.secondWord = secondWord;
    }

    /**
     *
     * @param thirdWord third word in criptarithm
     */
    public void setThirdWord(String thirdWord) {
        this.thirdWord = thirdWord;
    }

    /**
     *
     * @return first word
     */
    public String getFirstWord() {
        return this.firstWord;
    }

/**
 * 
 * @return  second Word
 */
    public String getSecondWord() {
        return this.secondWord;
    }

    /**
     *
     * @return return third word
     */
    public String getThirdWord() {
        return this.thirdWord;
    }

    /**
     * Allow to set all tree words
     * @param firstWord first word
     * @param secondWord second word
     * @param thirdWord third word
     */
    public void setWord(String firstWord, String secondWord, String thirdWord) {
        this.firstWord = firstWord;
        this.secondWord = secondWord;
        this.thirdWord = thirdWord;
    }

    /**
     * It creates weights. Giving word e.g. MARK, which consists of 4 letter and defining base e.g. 10
     * we will get weights [1000 100 10 1]
     * @param length length of the word
     * @param base base
     * @return array of weights
     */
    public static int[] createWeights(int length, int base) {

        int[] weights = new int[length];

        weights[length - 1] = 1;

        for (int i = length - 2; i >= 0; i--) {
            weights[i] = weights[i + 1] * base;
        }

        return weights;
    }

}
